<?php
namespace App\Services;

use Alipay\OpenAPISDK\Api\AlipaySystemOauthApi;
use Alipay\OpenAPISDK\Api\AlipayTradeApi;
use Alipay\OpenAPISDK\Api\AlipayUserAgreementApi;
use Alipay\OpenAPISDK\Model\AgreementParams;
use Alipay\OpenAPISDK\Model\AlipayTradeCreateModel;
use Alipay\OpenAPISDK\Model\AlipayTradePayModel;
use Alipay\OpenAPISDK\Model\AlipayTradeRefundModel;
use Alipay\OpenAPISDK\Model\AlipayUserAgreementUnsignModel;
use Alipay\OpenAPISDK\Model\GoodsDetail;
use Alipay\OpenAPISDK\Model\PayParams;
use Alipay\OpenAPISDK\Util\AlipayConfigUtil;
use Alipay\OpenAPISDK\Util\AlipayLogger;
use Alipay\OpenAPISDK\Util\AlipaySignature;
use Alipay\OpenAPISDK\Util\GenericExecuteApi;
use Alipay\OpenAPISDK\Util\Model\AlipayConfig;
use Alipay\OpenAPISDK\ApiException;
use Alipay\OpenAPISDK\Model\AlipaySystemOauthTokenModel;
use App\Enums\OrderEnum;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

class AliPayService {
    private $alipayConfigUtil;

    /**
     * 填充公共参数
     */
    public function __construct()
    {
        AlipayLogger::setNeedEnableLogger(false);

        $alipayConfig = new AlipayConfig();

        $alipayConfig->setServerUrl('https://openapi.alipay.com');

        $alipayConfig->setAppId(config('services.alipay.appId'));

        $alipayConfig->setEncryptKey(config('services.alipay.encryptKey'));

        $alipayConfig->setPrivateKey(config('services.alipay.privateKey'));

        $app_cert_path = 'file://'.base_path('cert/Alipay/app_cert_public_key.crt');
        $alipayConfig->setAppCertPath($app_cert_path);

        $root_cert_path = 'file://'.base_path('cert/Alipay/alipay_root_cert.crt');
        $alipayConfig->setRootCertPath($root_cert_path);

        $alipay_public_cert_path = 'file://'.base_path('cert/Alipay/alipay_cert_public_key.crt');
        $alipayConfig->setAlipayPublicCertPath($alipay_public_cert_path);

        $this->alipayConfigUtil = new AlipayConfigUtil($alipayConfig);
    }

    /**
     * @notes 验签
     * @param $params
     * @return bool
     * @throws ApiException
     * @author Evil丿
     * @date 2024/09/02 16:28
     */
    public function verifySign($params)
    {

        $alipay_public_cert_path = 'file://'.base_path('cert/Alipay/alipay_cert_public_key.crt');

        $sign = AlipaySignature::rsaCheckV1($params, $alipay_public_cert_path, 'RSA2', true);

        return $sign;
    }

    /**
     * @notes 获取用户OPENID
     * @param $code
     * @return mixed|null
     * @author Evil丿
     * @date 2024/09/02 15:38
     */
    public function getOpenId($code)
    {
        try {
            $params = [
                "code"              => $code,
                "grantType"        => 'authorization_code'
            ];

            $apiInstance = new AlipaySystemOauthApi();

            $apiInstance->setAlipayConfigUtil($this->alipayConfigUtil);

            $data = new AlipaySystemOauthTokenModel($params);

            $result = $apiInstance->token($data);

            return $result['openId'];

        } catch (ApiException $e) {
            Log::error(date('Ymd H:i:s', time()). ' : '.$e->getResponseBody());
            throw new BadRequestHttpException($e->getMessage());
        }
    }

    /**
     * @notes 解密获取用户手机号
     * @param $response
     * @return false|mixed
     * @author Evil丿
     * @date 2024/08/14 15:28
     */
    public function getPhoneNumber($response)
    {
        $response = urldecode($response);

        $result =  $this -> alipayConfigUtil -> decrypt($response);

        $v = json_decode($result, true);

        if(!is_array($v)){
            return false;
        }

        if($v['code'] == '10000') {
            return $v['mobile'];
        } else {
            return false;
        }
    }

    /**
     * @notes 获取用户签约状况
     * @param $out_trade_sn
     * @return array|false
     * @author Evil丿
     * @date 2024/09/02 15:17
     */
    public function getOrder($order_sn, $open_id)
    {
        try {
            $apiInstance = new AlipayUserAgreementApi();

            $apiInstance->setAlipayConfigUtil($this->alipayConfigUtil);

            $result = $apiInstance->query("ONE_TIME_AUTH_P",null,$open_id,null,"INDUSTRY|DEFAULT_SCENE", $order_sn);

            $state_desc = "";

            switch($result['status'])
            {
                case 'TEMP':
                    $state = OrderEnum::ORDER_CREATED;
                    break;
                case 'NORMAL':
                    $state = OrderEnum::ORDER_DOING;
                    $state_desc = "USER_CONFIRM";
                    break;
                case 'STOP':
                    $state = OrderEnum::ORDER_REVOKED;
                    break;
                default :
                    $state = OrderEnum::ORDER_CREATED;
                    break;
            }
            return [
                'state' => $state,
                'state_desc' => $state_desc
            ];

        } catch (ApiException $e) {
            Log::channel('alipay')->error('支付宝查询签约失败', ['error' => $e->getResponseBody()]);
            return false;
        }
    }

    /**
     * @notes 用户签约获取拉起弹窗参数
     * @param $order_sn
     * @param $machine_id
     * @return string
     * @author Evil丿
     * @date 2024/09/02 15:18
     */
    public function getAuthParams($order_sn, $machine_id)
    {
        $this->alipayConfigUtil->setEncryptKey('');

        $apiInstance = new GenericExecuteApi($this->alipayConfigUtil);

        $bizParams = [
            "biz_content" => [
                "personal_product_code" => "ONE_TIME_AUTH_P",
                "sign_scene"            => "INDUSTRY|DEFAULT_SCENE",
                "product_code"          => "ONE_TIME_AUTH",
                "external_agreement_no" => $order_sn,
                "access_params"         => [
                    "channel" => "ALIPAYAPP",
                ],
                "device_params"         => [
                    "out_device_id" => $machine_id
                ]
            ]
        ];

        try {
            $auth_params = $apiInstance -> sdkExecute("alipay.user.agreement.page.sign", $bizParams);

            return $auth_params;
        } catch (ApiException $e) {
            Log::channel('alipay')->error('支付宝签约失败', ['error' => $e->getResponseBody()]);
            throw new BadRequestHttpException($e->getMessage());
        }
    }

    /**
     * @notes 取消签约订单
     * @param $out_order_sn
     * @return void
     * @author Evil丿
     * @date 2024/09/02 15:18
     */
    public function cancelOrder($order_sn, $open_id)
    {
        $apiInstance = new AlipayUserAgreementApi();

        $apiInstance->setAlipayConfigUtil($this->alipayConfigUtil);

        $data = new AlipayUserAgreementUnsignModel();

        $data->setAlipayOpenId($open_id);

        $data->setExternalAgreementNo($order_sn);

        $data->setPersonalProductCode("ONE_TIME_AUTH_P");

        $data->setSignScene("INDUSTRY|DEFAULT_SCENE");

        try {
            $apiInstance->unsign($data);
        } catch (ApiException $e) {
            Log::channel('alipay')->error('支付宝解除签约失败', ['error' => $e->getResponseBody()]);
        }
    }

    /**
     * @notes 订单付款
     * @param $order_sn
     * @param $amount
     * @param $goods_list
     * @param $agreement_no
     * @return array
     * @throws ApiException
     * @author Evil丿
     * @date 2024/09/03 14:46
     */
    public function payOrder($order_sn, $amount, $goods_list, $agreement_no)
    {
        $apiInstance = new AlipayTradeApi();
        $apiInstance->setAlipayConfigUtil($this->alipayConfigUtil);
        $data = new AlipayTradePayModel();
        $data->setOutTradeNo($order_sn);
        $data->setTotalAmount(bcdiv($amount, 100 , 2));
        $data->setSubject("关门结算");
        $data->setProductCode("GENERAL_WITHHOLDING");
        $data->setNotifyUrl(config('app.url').'/api/swt/notify/alipay/order');
        $agreementParams = new AgreementParams();
        $agreementParams->setAgreementNo($agreement_no);
        $data->setAgreementParams($agreementParams);
        $pay_params = new PayParams();
        $pay_params -> setAsyncType('NORMAL_ASYNC');
        $goodsDetail = [];
        foreach($goods_list as $goods) {
            if($goods['amount'] > 0) {
                $goodsDetail0 = new GoodsDetail();
                $goodsDetail0->setGoodsId(MD5($goods['name']));
                $goodsDetail0->setGoodsName($goods['name']);
                $goodsDetail0->setQuantity((int) $goods['quantity']);
                $goodsDetail0->setPrice(bcdiv($goods['amount'], 100 ,2));
                $goodsDetail[] = $goodsDetail0;
            }
        }
        $data->setGoodsDetail($goodsDetail);
        $data->setIsAsyncPay(true);

        try {
            $apiInstance->pay($data);

        } catch (ApiException $e) {

            $res = json_decode($e -> getResponseBody(), true);

            if(!key_exists('async_payment_mode', $res) || $res['async_payment_mode'] != 'NORMAL_ASYNC_PAY') {
                Log::channel('alipay')->error('支付宝发起支付异常返回', ['response' => $e -> getResponseBody()]);
            }

        } finally {
            return [
                'state_description' => OrderEnum::ORDER_MCH_COMPLETE
            ];
        }
    }

    /**
     * @notes 退款订单
     * @param $transaction_id
     * @param $out_refund_no
     * @param $amount
     * @param $reason
     * @return bool
     * @author Evil丿
     * @date 2024/09/03 17:17
     */
    public function refundOrder($transaction_id, $out_refund_no, $amount, $reason)
    {
        $apiInstance = new AlipayTradeApi();

        $apiInstance->setAlipayConfigUtil($this->alipayConfigUtil);

        $data = new AlipayTradeRefundModel();

        $data->setTradeNo($transaction_id);
        $data->setRefundAmount(bcdiv($amount,100 , 2));
        $data->setOutRequestNo("RO".$out_refund_no);
        $data->setRefundReason($reason ?? "商家退款");

        try {
            $apiInstance->refund($data);

            return true;
        } catch (ApiException $e) {
            Log::channel('alipay')->error('支付宝退款失败', ['error' => $e->getResponseBody()]);
            return false;
        }
    }

    /**
     * @notes JSAPI支付
     * @param $order_sn
     * @param $amount
     * @param $open_id
     * @return \Alipay\OpenAPISDK\Model\AlipayTradeCreateDefaultResponse|\Alipay\OpenAPISDK\Model\AlipayTradeCreateResponseModel|false
     * @author Evil丿
     * @date 2024/09/03 17:28
     */
    public function payOrderByJsapi($order_sn, $amount, $open_id)
    {
        $apiInstance = new AlipayTradeApi();

        $apiInstance->setAlipayConfigUtil($this->alipayConfigUtil);
        $data = new AlipayTradeCreateModel();

        $data->setOutTradeNo($order_sn);

        $data->setTotalAmount(bcdiv($amount, 100 , 2));

        $data->setSubject("订单补单");

        $data->setProductCode("JSAPI_PAY");

        $data->setOpAppId(config('services.alipay.appId'));

        $data->setBuyerOpenId($open_id);

        $data->setNotifyUrl(config('app.url').'/api/swt/notify/alipay/order/jsapi');

        try {
            $result = $apiInstance->create($data);

            return $result;
        } catch (ApiException $e) {
            Log::channel('alipay')->error('支付宝JSAPI支付失败', ['error' => $e->getResponseBody()]);
            return false;
        }
    }
}
?>
